package no.sachmuld.methodcombination.api.annotation;

import no.sachmuld.methodcombination.api.reflection.component.MethodCombination;
import no.sachmuld.methodcombination.impl.annotation.*;
import no.sachmuld.methodcombination.impl.validate.BehaviourClassIsIncompatibleWithBehaviourChain;
import no.sachmuld.methodcombination.impl.validate.MethodCombinationValidationError;
import no.sachmuld.methodcombination.impl.validate.MissingBehaviourClassFromMethodCombinationProtocol;
import no.sachmuld.methodcombination.impl.validate.annotation.AmbiguousMethodCombinationAnnotations;
import no.sachmuld.methodcombination.impl.validate.annotation.MissingBehaviourAnnotation;
import no.sachmuld.methodcombination.impl.validate.annotation.MissingMethodCombinationDependency;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 *
 */
public class MethodCombinationValidator extends no.sachmuld.methodcombination.api.MethodCombinationValidator {

    private Class<?> targetClass;
    private Class behaviourClass;
    private Map<Method, MethodCombination> methodCombinationMap;

    public MethodCombinationValidator(Class<?> targetClass) {
        this.targetClass = targetClass;
    }

    @Override
    protected Class findTargetClass(List<MethodCombinationValidationError> errors) {
        return targetClass;
    }

    @Override
    protected Class findBehaviourClass(List<MethodCombinationValidationError> errors) {

        UsesMethodCombination usesMethodCombination = targetClass.getAnnotation(UsesMethodCombination.class);
        if (usesMethodCombination == null) {
            errors.add(new MissingBehaviourAnnotation(targetClass));
        } else {
            behaviourClass = usesMethodCombination.value();
        }

        Class behaviourClassFromBehaviourChain = BehaviourClassUtils.behaviourClassFromMethodCombinationProtocol(targetClass);
        if (behaviourClassFromBehaviourChain == null) {
            errors.add(new MissingBehaviourClassFromMethodCombinationProtocol(targetClass));
        }

        if (behaviourClass != null && behaviourClassFromBehaviourChain != null) {
            if (!behaviourClassFromBehaviourChain.isAssignableFrom(behaviourClass)) {
                errors.add(new BehaviourClassIsIncompatibleWithBehaviourChain(targetClass, behaviourClass, behaviourClassFromBehaviourChain));
            }
        }

        return behaviourClass;
    }

    @Override
    protected Set<Method> findTargetMethods(List<MethodCombinationValidationError> errors) {

        methodCombinationMap = new HashMap<>();

        for (Method method : targetClass.getDeclaredMethods()) {

            try {

                MethodCombination methodCombination = MethodCombinationInitializationUtils.initialize(method);

                if (methodCombination != null) {

                    methodCombination.build(behaviourClass, method);
                    methodCombinationMap.put(method, methodCombination);

                }

            } catch (AmbiguousMethodCombinationException e) {
                errors.add(new AmbiguousMethodCombinationAnnotations(targetClass, method));
            } catch (MethodCombinationDependencyException e) {
                errors.add(new MissingMethodCombinationDependency(targetClass, method));
            }

        }

        return methodCombinationMap.keySet();

    }

    @Override
    protected MethodCombination findMethodCombination(Method method, List<MethodCombinationValidationError> errors) {
        return methodCombinationMap.get(method);
    }

}
